import puppeteer, { type Browser, type ChromeReleaseChannel, type Page } from 'puppeteer';
import { createChildLogger } from '../../utils/logger.js';
import { LocalMermaidManager } from './mermaid-local.js';

/**
 * 基于 Puppeteer 的浏览器管理器
 */
export abstract class PuppeteerBrowserManagerBase {
  protected log = createChildLogger('convert/puppeteer-browser');
  protected async withBrowser<T>(operation: (browser: Browser) => Promise<T>): Promise<T> {
    let browser: Browser | null = null;
    try {
      const launchOptions = await this.getBrowserConfig();
      browser = await puppeteer.launch(launchOptions);
      const version = await browser.version();
      this.log.info(
        `🚀 [Puppeteer] 浏览器实例已启动 -> ${version}; channel=${
          (launchOptions as any).channel ?? 'default'
        }; exec=${(launchOptions as any).executablePath ?? 'default'}`
      );

      // 总操作超时（3分钟，避免长文档被误判）
      let opTimeout: ReturnType<typeof setTimeout> | undefined;
      const timeoutPromise = new Promise<never>((_, reject) => {
        const OPERATION_TIMEOUT_MS = 180_000;
        opTimeout = setTimeout(
          () => reject(new Error('[Puppeteer] 浏览器操作超时')),
          OPERATION_TIMEOUT_MS
        );
        // 不阻止进程退出
        (opTimeout as any)?.unref?.();
      });

      try {
        return await Promise.race([operation(browser), timeoutPromise]);
      } finally {
        if (opTimeout) clearTimeout(opTimeout);
      }
    } catch (e) {
      this.log.error(e instanceof Error ? e.message : '未知错误');
      throw e;
    } finally {
      if (browser) {
        try {
          await browser.close();
          this.log.info('🔄 [Puppeteer] 浏览器实例已关闭');
        } catch (error) {
          this.log.warn(
            '⚠️ [Puppeteer] 关闭浏览器失败:',
            error instanceof Error ? error.message : String(error)
          );
        }
      }
    }
  }

  protected async withPage<T>(operation: (page: Page) => Promise<T>): Promise<T> {
    return await this.withBrowser(async browser => {
      const page = await browser.newPage();

      try {
        await page.setRequestInterception(true);
        page.on('request', req => {
          const url = req.url();
          if (url.startsWith('http://') || url.startsWith('https://')) {
            return req.abort();
          }
          return req.continue();
        });
      } catch (e) {
        this.log.warn('[Puppeteer] 设置请求拦截失败:', e instanceof Error ? e.message : String(e));
      }

      try {
        return await operation(page);
      } finally {
        try {
          await page.close();
        } catch {}
      }
    });
  }

  protected async getBrowserConfig(): Promise<Parameters<typeof puppeteer.launch>[0]> {
    const channelEnv = process.env.BROWSER_CHANNEL?.trim();
    const executablePath = process.env.BROWSER_EXECUTABLE?.trim();
    let channel: ChromeReleaseChannel | undefined;
    if (
      channelEnv === 'chrome' ||
      channelEnv === 'chrome-beta' ||
      channelEnv === 'chrome-canary' ||
      channelEnv === 'chrome-dev'
    ) {
      channel = channelEnv as ChromeReleaseChannel;
    }
    return {
      headless: true,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
        '--no-zygote',
        '--disable-software-rasterizer',
        '--disable-background-timer-throttling',
        '--disable-backgrounding-occluded-windows',
        '--disable-renderer-backgrounding',
        '--no-default-browser-check',
        '--no-first-run',
        '--disable-default-apps',
        '--disable-extensions',
        '--disable-translate',
        '--metrics-recording-only',
        '--mute-audio',
      ],
      protocolTimeout: 120_000,
      ...(channel ? { channel } : {}),
      ...(executablePath ? { executablePath } : {}),
    };
  }
}

export class DocumentBrowserManagerPuppeteer extends PuppeteerBrowserManagerBase {
  private renderCount = 0;

  async renderMermaidToImage(
    mermaidCode: string,
    outputPath: string,
    options: { width?: number; height?: number; chineseFont?: string; theme?: string } = {}
  ): Promise<string | null> {
    try {
      const result = await this.withPage(async page => {
        this.log.info(`📊 [Puppeteer] 开始渲染Mermaid图表 #${this.renderCount + 1}`);

        await page.setViewport({ width: options.width || 600, height: options.height || 400 });
        const mermaidScript = await LocalMermaidManager.getMermaidScript();
        const chineseFont = options.chineseFont || 'Microsoft YaHei';
        const theme = options.theme || 'default';

        const html = `<!DOCTYPE html><html><head><meta charset="utf-8" />
<style>body{margin:0;padding:20px;font-family:${chineseFont},sans-serif;background:white}.mermaid{text-align:center}</style>
<script>${mermaidScript}</script>
<script>
  document.addEventListener('DOMContentLoaded', async () => {
    try {
      if (typeof mermaid !== 'undefined') {
        mermaid.initialize({ startOnLoad: false, theme: '${theme}', fontFamily: '${chineseFont}', fontSize: 14, securityLevel: 'loose', logLevel: 'error' });
        const el = document.querySelector('.mermaid');
        if (el && el.textContent.trim()) {
          try {
            const { svg } = await mermaid.render('graph', el.textContent);
            el.innerHTML = svg;
          } catch (e) {
            el.innerHTML = '<div style="padding:20px;background:#fff3cd;border:2px solid #ffc107;border-radius:4px;text-align:center;">📊 Mermaid图表渲染失败</div>';
          }
        }
      } else {
        document.querySelector('.mermaid').innerHTML = '<div style="padding:20px;background:#e3f2fd;border:2px solid #2196f3;border-radius:4px;text-align:center;">📊 离线模式</div>';
      }
    } catch (e) {}
  });
</script>
</head><body><div class="mermaid">${mermaidCode}</div></body></html>`;

        // 设置内容
        await page.setContent(html, { waitUntil: 'domcontentloaded', timeout: 60_000 });

        // 等待渲染完成（SVG或错误）
        try {
          await page.waitForFunction(
            () => {
              const el = document.querySelector('.mermaid');
              return (
                el &&
                (el.innerHTML.includes('svg') ||
                  el.innerHTML.includes('渲染失败') ||
                  el.innerHTML.includes('离线模式'))
              );
            },
            { timeout: 30_000 }
          );
        } catch {}

        const el = await page.$('.mermaid');
        if (!el) throw new Error('未找到Mermaid元素');
        const pngPath = (
          outputPath.endsWith('.png') ? outputPath : `${outputPath}.png`
        ) as `${string}.png`;
        await el.screenshot({ path: pngPath });

        this.renderCount++;
        this.log.info(`🎨 [Puppeteer] Mermaid图表渲染完成 #${this.renderCount}: ${outputPath}`);
        return outputPath;
      });

      return result;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.log.warn(`❌ [Puppeteer] Mermaid图表渲染失败: ${errorMessage}`);
      return `error:${errorMessage}`;
    }
  }

  async generatePdf(htmlContent: string, outputPath: string, pdfOptions: any): Promise<Buffer> {
    return await this.withPage(async page => {
      await page.setContent(htmlContent, { waitUntil: 'domcontentloaded', timeout: 60_000 });
      try {
        await page.waitForFunction(
          () => {
            const els = document.querySelectorAll('.mermaid');
            if (els.length === 0) return true;
            return Array.from(els).every(
              el =>
                el.innerHTML.includes('svg') ||
                el.innerHTML.includes('渲染失败') ||
                el.innerHTML.includes('空的图表定义') ||
                el.innerHTML.includes('加载失败') ||
                el.innerHTML.includes('离线模式')
            );
          },
          { timeout: 60_000 }
        );
      } catch {}

      // 模拟打印媒体以提升版式一致性
      try {
        await page.emulateMediaType('print');
      } catch {}

      // 生成PDF
      const buffer = await page.pdf({ path: outputPath, ...pdfOptions });
      this.log.info(`📄 [Puppeteer] PDF生成完成: ${outputPath}`);
      return buffer as unknown as Buffer;
    });
  }

  getRenderCount(): number {
    return this.renderCount;
  }
}
